MEGpipeline_PreprocMEG.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  2. % Reads raw data with Fieldtrip & applies preprocessing options. %
  3. % Inputs: Raw MEG data compatible with Fieldtrip. %
  4. % Last modified: Jan. 15, 2014 %
  5. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  6. % Copyright (C) 2013-2014, Michael J. Cheung
  7. %
  8. % This file is a part of the MEG & PLS Pipeline (MEGPLS). For more
  9. % details, see the documentation included with the software package.
  10. %
  11. % MEGPLS is free software: you can redistribute it and/or modify it under
  12. % the terms of the GNU General Public License version 2 as published by
  13. % the Free Software Foundation. This program is distributed in the hope
  14. % that it will be useful, but WITHOUT ANY WARRANTY; without even the
  15. % implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16. % See the GNU General Public License for more details.
  17. %
  18. % You should have received a copy of the GNU General Public License along
  19. % with this program. If not, you can download the license here:
  20. % <http://www.gnu.org/licenses/old-licenses/gpl-2.0>.
  21. function MEGpipeline_PreprocMEG(PreprocSettingsMat, PreprocInputMEG)
  22. % Make sure java paths added:
  23. UseProgBar = CheckParforJavaPaths;
  24. % Load .mat files from preprocessingGUI:
  25. LoadSettings = load(PreprocSettingsMat);
  26. time = LoadSettings.time;
  27. name = PreprocInputMEG.name;
  28. paths = PreprocInputMEG.paths;
  29. TargetMarkers = PreprocInputMEG.epoch.TargetMarkers;
  30. IncludedEvents = PreprocInputMEG.epoch.IncludedEvents;
  31. ExcludedEvents = PreprocInputMEG.epoch.ExcludedEvents;
  32. DataFiletype = PreprocInputMEG.gui.DataFiletype;
  33. % Set up errorlog and diary:
  34. if exist('ErrorLog_PreprocMEG.txt', 'file')
  35. system('rm ErrorLog_PreprocMEG.txt');
  36. end
  37. if exist('Diary_PreprocMEG.txt', 'file')
  38. system('rm Diary_PreprocMEG.txt');
  39. end
  40. diary Diary_PreprocMEG.txt
  41. ErrLog = fopen('ErrorLog_PreprocMEG.txt', 'a');
  42. %==============================================%
  43. % EPOCH/READ IN AND PREPROCESS INPUT MEG DATA: %
  44. %==============================================%
  45. NumSubj = length(name.SubjID);
  46. if UseProgBar == 1
  47. ppm = ParforProgMon('PREPROCESSING MEG DATASETS: ', NumSubj, 1, 700, 80);
  48. end
  49. parfor s = 1:length(name.SubjID)
  50. % Check if files specified and exist:
  51. if isempty(paths.DataFullpath{s})
  52. disp(['Warning: Input data for: ',name.SubjID,' was not specified.']);
  53. disp('Skipping Subject.')
  54. continue;
  55. end
  56. if ~exist(paths.DataFullpath{s}, 'file')
  57. ErrMsg = sprintf(['ERROR: Input MEG file does not exist:'...
  58. '\n %s \n\n'], paths.DataFullpath{s});
  59. LogErrorMessage(ErrMsg, 'PreprocMEG');
  60. disp('ERROR: Input MEG file does not exist. Skipping subject.')
  61. continue;
  62. end
  63. [~, ~, FileExt] = fileparts(paths.DataFullpath{s});
  64. %--- Read Header and Event info from input datasets: ---%
  65. %-------------------------------------------------------%
  66. % If reading from raw dataset format:
  67. if ~strcmp(FileExt, '.mat')
  68. try
  69. Hdr = [];
  70. Hdr = ft_read_header(paths.DataFullpath{s}); % For sampling-rate
  71. catch
  72. ErrMsg = sprintf(['ERROR: Dataset header info could not be read into FT:'...
  73. '\n SKIPPING dataset: %s \n\n'], paths.DataFullpath{s});
  74. LogErrorMessage(ErrMsg, 'PreprocMEG');
  75. disp('ERROR: Failed to read dataset. Header could not be read into FT.')
  76. continue;
  77. end
  78. try
  79. AllEventInfo = [];
  80. AllEventInfo = ft_read_event(paths.DataFullpath{s});
  81. catch
  82. ErrMsg = sprintf(['ERROR: Dataset event info could not be read by FT:'...
  83. '\n SKIPPING dataset: %s \n\n'], paths.DataFullpath{s});
  84. LogErrorMessage(ErrMsg, 'PreprocMEG');
  85. disp('ERROR: Failed to read event info from dataset.')
  86. continue;
  87. end
  88. end
  89. % If reading from FieldTrip .mat file:
  90. if strcmp(FileExt, '.mat')
  91. AllEventInfo = [];
  92. DatasetMat = [];
  93. DatasetMat = LoadFTmat(paths.DataFullpath{s}, 'PreprocMEG');
  94. if isempty(DatasetMat)
  95. continue; % error loading
  96. end
  97. if isfield(DatasetMat, 'AllEventInfo')
  98. AllEventInfo = DatasetMat.AllEventInfo;
  99. else
  100. if ~isempty(IncludedEvents) || ~isempty(ExcludedEvents)
  101. ErrMsg = sprintf(['ERROR: Inc./Exc. events specified but dataset does not'...
  102. 'contain event info field. \n As a result, cannot search if specified'...
  103. 'events are within trials. \n Note: AllEventInfo field is present in FT'...
  104. '.mat files created through [MEG]PLS. \n SKIPPING dataset: %s \n\n'], ...
  105. paths.DataFullpath{s});
  106. LogErrorMessage(ErrMsg, 'PreprocMEG');
  107. disp('ERROR: Inc./Exc. events specified, but event info field missing.')
  108. continue;
  109. end
  110. end
  111. end
  112. %--- Define trial definition info for datasets: ---%
  113. %--------------------------------------------------%
  114. % Results in Nx3 trial definition where:
  115. % (N,1) = BegSample of trial relative to start of raw data.
  116. % (N,2) = EndSample of trial relative to start of raw data.
  117. % (N,3) = Offset where negative means trial begins prior to marker, etc.
  118. % For raw datasets read in as is (no T=0 events have been specified):
  119. % Ex: Reading in data already epoched or reading data in as continuous.
  120. if ~strcmp(FileExt, '.mat') && isempty(TargetMarkers)
  121. EpochTrialInfo = [];
  122. trl = [];
  123. if Hdr.nTrials == 1 % Reading continuous dataset
  124. trl = zeros(1,3);
  125. trl(1,1) = 1;
  126. trl(1,2) = Hdr.nSamples * Hdr.nTrials;
  127. trl(1,3) = -Hdr.nSamplesPre;
  128. else % Reading dataset already epoched
  129. trl = zeros(Hdr.nTrials, 3);
  130. for i = 1:Hdr.nTrials
  131. trl(i,1) = (i-1)*Hdr.nSamples + 1;
  132. trl(i,2) = (i )*Hdr.nSamples ;
  133. trl(i,3) = -Hdr.nSamplesPre;
  134. end
  135. end
  136. EpochTrialInfo = trl;
  137. end
  138. % For raw datasets being epoched by specified T=0 events:
  139. % Define trial info for each target T=0 event found.
  140. if ~strcmp(FileExt, '.mat') && ~isempty(TargetMarkers)
  141. EpochTrialInfo = [];
  142. cfgDefineTrial = [];
  143. cfgDefineTrial = LoadSettings.FTcfg.DefineTrial;
  144. cfgDefineTrial.dataset = paths.DataFullpath{s};
  145. for e = 1:size(TargetMarkers, 1)
  146. cfgDefineTrial.trialdef.eventtype = TargetMarkers{e,1};
  147. cfgDefineTrial.trialdef.eventvalue = TargetMarkers{e,2};
  148. try
  149. CurrentEventInfo = ft_definetrial(cfgDefineTrial);
  150. EpochTrialInfo = [EpochTrialInfo; CurrentEventInfo.trl];
  151. catch
  152. ErrMsg = sprintf(['\nWARNING: Could not find this target T=0 event'...
  153. 'in the current dataset. \n - Dataset: %s \n - Event: %s \n\n'], ...
  154. paths.DataFullpath{s}, TargetMarkers{e,3});
  155. LogErrorMessage(ErrMsg, 'PreprocMEG');
  156. end
  157. end
  158. % Check for situation that no trials defined (no T=0 events found):
  159. if isempty(EpochTrialInfo)
  160. ErrMsg = sprintf(['ERROR: Failed to define trials for current dataset.'...
  161. '\n None of the target T=0 events were found in the dataset.'...
  162. '\n %s \n\n'], paths.DataFullpath{s});
  163. LogErrorMessage(ErrMsg, 'PreprocMEG');
  164. disp('ERROR: Failed to define trials for dataset. Specified T=0 events not found.')
  165. continue;
  166. end
  167. % Sort trials in order by samples:
  168. EpochTrialInfo = sortrows(EpochTrialInfo, 1);
  169. end
  170. % For FieldTrip .mat datasets:
  171. if strcmp(FileExt, '.mat')
  172. EpochTrialInfo = [];
  173. EpochTrialInfo = DatasetMat.sampleinfo; % sampleinfo is up-to-date with dataset.
  174. % Get offset info for dataset:
  175. % Note: The .trl field is not always kept up-to-date by FT like sampleinfo is.
  176. MissingOffsetInfo = 0;
  177. % If immediate .cfg does not contain .trl or .offset, check if nested in .previous:
  178. % If both fields are nested, cannot tell which one is more recent:
  179. if ~isfield(DatasetMat.cfg, 'trl') && ~isfield(DatasetMat.cfg, 'offset')
  180. GetOffsetCfg = ft_findcfg(DatasetMat.cfg, 'offset');
  181. GetTrlCfg = ft_findcfg(DatasetMat.cfg, 'trl');
  182. if ~isempty(GetOffsetCfg) && ~isempty(GetTrlCfg)
  183. MissingOffsetInfo = 1;
  184. end
  185. end
  186. % Check if immediate cfg.offset is present:
  187. if isfield(DatasetMat.cfg, 'offset')
  188. if length(DatasetMat.cfg.offset) == 1 || ...
  189. isequal(size(EpochTrialInfo, 1), length(DatasetMat.cfg.offset))
  190. EpochTrialInfo(:,3) = DatasetMat.cfg.offset;
  191. else
  192. MissingOffsetInfo = 1;
  193. end
  194. % Otherwise, check for .trl field:
  195. else
  196. GetTrlCfg = ft_findcfg(DatasetMat.cfg, 'trl');
  197. if isequal(EpochTrialInfo(:,[1:2]), GetTrlCfg(:,[1:2])) % If .trls up-to-date
  198. EpochTrialInfo(:,3) = GetTrlCfg(:,3);
  199. else % If .trls not up-to-date:
  200. for t = 1:size(EpochTrialInfo, 1) % Get offset from respective matching trial
  201. MatchTrial = find(ismember(GetTrlCfg(:,[1:2]), EpochTrialInfo(t,[1:2]), 'rows'));
  202. if ~isempty(MatchTrial) && length(MatchTrial) == 1
  203. EpochTrialInfo(t,3) = GetTrlCfg(MatchTrial, 3);
  204. else
  205. MissingOffsetInfo = 1;
  206. end
  207. end
  208. end
  209. end
  210. % Display warnings:
  211. if MissingOffsetInfo == 1
  212. if ~isempty(IncludedEvents) || ~isempty(ExcludedEvents)
  213. ErrMsg = sprintf(['ERROR: Cannot determine trial offset info for dataset.'...
  214. '\n As a result, cannot search if inc./exc. events are within trials.'...
  215. '\n To fix, use "ft_redefinetrial" on dataset to define cfg.trl field.'...
  216. '\n SKIPPING dataset: %s \n\n'], paths.DataFullpath{s});
  217. LogErrorMessage(ErrMsg, 'PreprocMEG');
  218. disp('ERROR: Cannot search for inc./exc. events for dataset. See ErrLog.')
  219. continue;
  220. end
  221. ErrMsg = sprintf(['WARNING: Cannot determine trial offset info for dataset.'...
  222. '\n As a result, could not check if trials exceeded specified overlap.'...
  223. '\n All trials will be kept included regardless of overlap amount.'...
  224. '\n To fix, use "ft_redefinetrial" on dataset to define cfg.trl field.'...
  225. '\n Dataset: %s \n\n'], paths.DataFullpath{s});
  226. LogErrorMessage(ErrMsg, 'PreprocMEG');
  227. end
  228. end % If .mat
  229. %--- Remove trials that exceed overlap threshold: ---%
  230. %----------------------------------------------------%
  231. % Note: The "hdr" field in FT .mat files is not kept up-to-date.
  232. % - It represents the header of the ORIGINAL .ds file read into FT.
  233. % - Therefore, if reading in FT .mat, get sampling rate from fsample.
  234. if ~strcmp(FileExt, '.mat')
  235. SamplingRate = Hdr.Fs; % Get from .ds
  236. elseif strcmp(FileExt, '.mat')
  237. SamplingRate = DatasetMat.fsample; % Get from .mat structure
  238. end
  239. ThresholdSamples = round(time.OverlapThresh * SamplingRate);
  240. RejectTrials = [];
  241. for n = 2:size(EpochTrialInfo, 1)
  242. PrevTrialEnd = EpochTrialInfo(n-1, 2);
  243. CurrTrialStart = EpochTrialInfo(n,1);
  244. SampleOverlap = PrevTrialEnd - CurrTrialStart;
  245. if SampleOverlap < 0
  246. continue; % No overlap with previous trial.
  247. else
  248. if SampleOverlap > ThresholdSamples
  249. RejectTrials = [RejectTrials, n-1, n]; % Reject both overlap trials.
  250. end
  251. end
  252. end
  253. RejectTrials = unique(RejectTrials);
  254. EpochTrialInfo(RejectTrials,:) = [];
  255. %--- Isolate for trials with included events in search-range: ---%
  256. %----------------------------------------------------------------%
  257. if ~isempty(IncludedEvents)
  258. % Get sample info of when included events occurred:
  259. IncEventSamples = [];
  260. for e = 1:size(IncludedEvents, 1);
  261. EventType = IncludedEvents{e,1};
  262. EventInfo = AllEventInfo(ismember({AllEventInfo.type}, EventType));
  263. EventValue = IncludedEvents{e,2};
  264. if ~isempty(EventValue)
  265. EventInfo = EventInfo(ismember([EventInfo.value], EventValue));
  266. end
  267. IncEventSamples = [IncEventSamples, EventInfo.sample];
  268. IncEventSamples = sort(IncEventSamples);
  269. end
  270. % Get time-range of data (in samples) to search for IncludedEvents:
  271. SampleSearchRange = [];
  272. for n = 1:size(EpochTrialInfo, 1)
  273. TrialBegSample = EpochTrialInfo(n,1);
  274. TrialEndSample = EpochTrialInfo(n,2);
  275. TrialOffset = EpochTrialInfo(n,3);
  276. StartSearchSample = TrialBegSample - TrialOffset + ...
  277. (round(time.IncEventStart * SamplingRate));
  278. EndSearchSample = TrialBegSample - TrialOffset + ...
  279. (round(time.IncEventEnd * SamplingRate));
  280. if StartSearchSample < TrialBegSample
  281. StartSearchSample = TrialBegSample;
  282. end
  283. if EndSearchSample > TrialEndSample
  284. EndSearchSample = TrialEndSample;
  285. end
  286. SampleSearchRange(n,:) = [StartSearchSample:EndSearchSample];
  287. end
  288. % Only keep trials where included events found in search-range:
  289. RejectTrials = [];
  290. for n = 1:size(EpochTrialInfo, 1)
  291. if max(ismember(SampleSearchRange(n,:), IncEventSamples)) ~= 1
  292. RejectTrials = [RejectTrials, n];
  293. end
  294. end
  295. RejectTrials = unique(RejectTrials);
  296. EpochTrialInfo(RejectTrials,:) = [];
  297. end
  298. %--- Exclude trials with excluded events in search-range: ---%
  299. %------------------------------------------------------------%
  300. if ~isempty(ExcludedEvents)
  301. % Get sample info of when excluded events occurred:
  302. ExcEventSamples = [];
  303. for e = 1:size(ExcludedEvents, 1);
  304. EventType = ExcludedEvents{e,1};
  305. EventInfo = AllEventInfo(ismember({AllEventInfo.type}, EventType));
  306. EventValue = ExcludedEvents{e,2};
  307. if ~isempty(EventValue)
  308. EventInfo = EventInfo(ismember([EventInfo.value], EventValue));
  309. end
  310. ExcEventSamples = [ExcEventSamples, EventInfo.sample];
  311. ExcEventSamples = sort(ExcEventSamples);
  312. end
  313. % Get time-range of data (in samples) to search for ExcludedEvents:
  314. SampleSearchRange = [];
  315. for n = 1:size(EpochTrialInfo, 1)
  316. TrialBegSample = EpochTrialInfo(n,1);
  317. TrialEndSample = EpochTrialInfo(n,2);
  318. TrialOffset = EpochTrialInfo(n,3);
  319. StartSearchSample = TrialBegSample - TrialOffset + ...
  320. (round(time.ExcEventStart * SamplingRate));
  321. EndSearchSample = TrialBegSample - TrialOffset + ...
  322. (round(time.ExcEventEnd * SamplingRate));
  323. if StartSearchSample < TrialBegSample
  324. StartSearchSample = TrialBegSample;
  325. end
  326. if EndSearchSample > TrialEndSample
  327. EndSearchSample = TrialEndSample;
  328. end
  329. SampleSearchRange(n,:) = [StartSearchSample:EndSearchSample];
  330. end
  331. % Reject trials where excluded events found in search-range:
  332. RejectTrials = [];
  333. for n = 1:size(EpochTrialInfo, 1)
  334. if max(ismember(SampleSearchRange(n,:), ExcEventSamples)) == 1
  335. RejectTrials = [RejectTrials, n];
  336. end
  337. end
  338. RejectTrials = unique(RejectTrials);
  339. EpochTrialInfo(RejectTrials,:) = [];
  340. end
  341. %--- Epoch/Read in data and apply preprocessing: ---%
  342. %---------------------------------------------------%
  343. if isempty(EpochTrialInfo)
  344. ErrMsg = sprintf(['ERROR: Current event settings result in no trials for dataset:'...
  345. '\n %s \n\n'], paths.DataFullpath{s});
  346. LogErrorMessage(ErrMsg, 'PreprocMEG');
  347. disp('ERROR: Event parameters result in no trials for current dataset.')
  348. continue;
  349. end
  350. % If raw dataset format:
  351. if ~strcmp(FileExt, '.mat')
  352. cfgPreproc = [];
  353. cfgPreproc = LoadSettings.FTcfg.Preproc;
  354. cfgPreproc.dataset = paths.DataFullpath{s};
  355. cfgPreproc.channel = 'all';
  356. cfgPreproc.method = 'trial';
  357. cfgPreproc.trl = EpochTrialInfo;
  358. if Hdr.nTrials == 1 % If raw dataset is continuous
  359. cfgPreproc.padtype = 'data';
  360. else % If raw dataset already epoched
  361. cfgPreproc.padtype = 'mirror';
  362. end
  363. MEGdata = [];
  364. MEGdata = ft_preprocessing(cfgPreproc);
  365. % If FieldTrip .mat:
  366. elseif strcmp(FileExt, '.mat')
  367. cfgPreproc = [];
  368. cfgPreproc = LoadSettings.FTcfg.Preproc;
  369. cfgPreproc.channel = 'all';
  370. cfgPreproc.method = 'trial';
  371. cfgPreproc.padtype = 'mirror';
  372. DatasetMat = []; % Free memory
  373. cfgPreproc.inputfile = paths.DataFullpath{s};
  374. MEGdata = [];
  375. MEGdata = ft_preprocessing(cfgPreproc);
  376. if MissingOffsetInfo == 0
  377. cfgRedefine = [];
  378. cfgRedefine.trl = EpochTrialInfo;
  379. MEGdata = ft_redefinetrial(cfgRedefine, MEGdata);
  380. end
  381. end
  382. % Run CTF gradient-correction if specified:
  383. cfgGradCorrectCTF = [];
  384. cfgGradCorrectCTF = LoadSettings.FTcfg.GradCorrectCTF;
  385. if ~strcmp(cfgGradCorrectCTF, 'none')
  386. MEGdata = ft_denoise_synthetic(cfgGradCorrectCTF, MEGdata);
  387. end
  388. % Add event info into MEGdata structure in case of future preprocessing:
  389. if ~isfield(MEGdata, 'AllEventInfo')
  390. MEGdata.AllEventInfo = AllEventInfo;
  391. end
  392. %--- Save preprocessed data: ---%
  393. %-------------------------------%
  394. GroupFolder = ['GroupID_',name.CurrentGroupID];
  395. CondID = name.CurrentCondID;
  396. OutputFullpath = ...
  397. [paths.DataID,'/',GroupFolder,'/',CondID,'/',name.SubjID{s},'_PreprocMEG.mat'];
  398. CheckSavePath(OutputFullpath, 'PreprocMEG');
  399. ParforSaveMEGdata(OutputFullpath, MEGdata);
  400. MEGdata = []; % Free memory
  401. EpochTrialInfo = [];
  402. AllEventInfo = [];
  403. Hdr = [];
  404. if UseProgBar == 1 && mod(s, 1) == 0
  405. ppm.increment(); % move up progress bar
  406. end
  407. end % Subj
  408. if UseProgBar == 1
  409. ppm.delete();
  410. end
  411. %=========================%
  412. % CHECK FOR OUTPUT FILES: %
  413. %=========================%
  414. for s = 1:length(name.SubjID)
  415. GroupFolder = ['GroupID_',name.CurrentGroupID];
  416. CondID = name.CurrentCondID;
  417. OutputFullpath = ...
  418. [paths.DataID,'/',GroupFolder,'/',CondID,'/',name.SubjID{s},'_PreprocMEG.mat'];
  419. if ~exist(OutputFullpath, 'file')
  420. fprintf(ErrLog, ['ERROR: Missing MEG preprocessed output file for:'...
  421. '\n %s \n\n'], OutputFullpath);
  422. end
  423. end
  424. %=================%
  425. if exist([pwd,'/ErrorLog_PreprocMEG.txt'], 'file')
  426. LogCheck = dir('ErrorLog_PreprocMEG.txt');
  427. if LogCheck.bytes ~= 0 % File not empty
  428. open('ErrorLog_PreprocMEG.txt');
  429. else
  430. delete('ErrorLog_PreprocMEG.txt');
  431. end
  432. end
  433. fclose(ErrLog);
  434. diary off
  435. end
  436. function ParforSaveMEGdata(OutputPath, MEGdata)
  437. MEGdata;
  438. save(OutputPath, 'MEGdata');
  439. end